#include "pch.hpp"
#include "CBallMesh.hpp"
#include "item.h"
#include "animation.h"
#include "hud.h"

using namespace std;

extern std::vector<item> items;
//static sf::Vector2f itemPos;
//static animation snake_anim;
//static animation snake_blink_anim;
//static sf::Clock snake_clock;
//static animation snake_head_burn;
//static animation luska_dead;
//
//const float NEXT_BALL_HIDE_TIME = 0.025f;

sf::Vector2i CBallMesh::FindFirstFree(bool bWithoutNeighbours)
{
	sf::Vector2i i2Indices = sf::Vector2i(0, 0);
	float fMinLen = 10000.0f, fLen;
	for (int i = 0 ; i < MESH_SIZE ; ++i)
	{
		for (int j = 0 ; j < MESH_SIZE ; ++j)
		{
			if (aeMesh[i][j] == BallType::Empty)
			{
				fLen = Distance(GetWorldPosition(sf::Vector2i(MESH_SIZE / 2, MESH_SIZE / 2)), GetWorldPosition(sf::Vector2i(i, j)));
				if (fLen < fMinLen && (bWithoutNeighbours || HasNeighbours(sf::Vector2i(i, j))))
				{
					i2Indices = sf::Vector2i(i, j);
					fMinLen = fLen;
				}
			}
		}
	}
	return i2Indices;
}

void CBallMesh::AddBall(sf::Vector2i i2Indices, BallType::Type eType)
{
	if (i2Indices.x == -1)
	{
		i2Indices = FindFirstFree(true);
	}
	if (eType == BallType::Empty)
	{
		eType = BallType::Type(rand() % g_iBallCount);
	}
	aeMesh[i2Indices.x][i2Indices.y] = eType;
	++iBallCount;
	if (iBallCount == 200)
	{
		bHamsterLose = true;
	}
}

void CBallMesh::ColourPoint(sf::Vector2i i2Point, int iColour)
{
	int x = i2Point.x, y = i2Point.y;
	aiColourMesh[x][y] = iColour;
	if (x > 0 && y > 0 && aeMesh[x - 1][y - 1] != BallType::Empty
		&& aiColourMesh[x - 1][y - 1] == -1)
	{
		ColourPoint(sf::Vector2i(x - 1, y - 1), iColour);
	}
	if (y > 0 && aeMesh[x][y - 1] != BallType::Empty
		&& aiColourMesh[x][y - 1] == -1)
	{
		ColourPoint(sf::Vector2i(x, y - 1), iColour);
	}
	if (x > 0 && aeMesh[x - 1][y] != BallType::Empty
		&& aiColourMesh[x - 1][y] == -1)
	{
		ColourPoint(sf::Vector2i(x - 1, y), iColour);
	}
	if (x + 1 < MESH_SIZE && aeMesh[x + 1][y] != BallType::Empty
		&& aiColourMesh[x + 1][y] == -1)
	{
		ColourPoint(sf::Vector2i(x + 1, y), iColour);
	}
	if (y + 1 < MESH_SIZE && aeMesh[x][y + 1] != BallType::Empty
		&& aiColourMesh[x][y + 1] == -1)
	{
		ColourPoint(sf::Vector2i(x, y + 1), iColour);
	}
	if (x + 1 < MESH_SIZE && y + 1 < MESH_SIZE && aeMesh[x + 1][y + 1] != BallType::Empty
		&& aiColourMesh[x + 1][y + 1] == -1)
	{
		ColourPoint(sf::Vector2i(x + 1, y + 1), iColour);
	}
}

void CBallMesh::ColourMesh(void)
{
	iColourCount = 0;
	for (int i = 0 ; i < MESH_SIZE ; ++i)
	{
		for (int j = 0 ; j < MESH_SIZE ; ++j)
		{
			aiColourMesh[i][j] = -1;
		}
	}
	for (int i = 0 ; i < MESH_SIZE ; ++i)
	{
		for (int j = 0 ; j < MESH_SIZE ; ++j)
		{
			if (aeMesh[i][j] != BallType::Empty && aiColourMesh[i][j] == -1)
			{
				ColourPoint(sf::Vector2i(i, j), iColourCount++);
			}
		}
	}
}

void CBallMesh::LeaveBiggestGroup(void)
{
	vector<int> viColourNumbers;
	viColourNumbers.resize(iColourCount);
	for (int i = 0 ; i < viColourNumbers.size() ; ++i)
	{
		viColourNumbers[i] = 0;
	}
	for (int i = 0 ; i < MESH_SIZE ; ++i)
	{
		for (int j = 0 ; j < MESH_SIZE ; ++j)
		{
			if (aiColourMesh[i][j] != -1)
			{
				++viColourNumbers[aiColourMesh[i][j]];
			}
		}
	}
	iBallCount = 0;
	int iBiggestColour;
	for (int i = 0 ; i < viColourNumbers.size() ; ++i)
	{
		if (viColourNumbers[i] > iBallCount)
		{
			iBallCount = viColourNumbers[i];
			iBiggestColour = i;
		}
	}
	for (int i = 0 ; i < MESH_SIZE ; ++i)
	{
		for (int j = 0 ; j < MESH_SIZE ; ++j)
		{
			if (aiColourMesh[i][j] != iBiggestColour)
			{
				aeMesh[i][j] = BallType::Empty;
			}
		}
	}
}

vector<sf::Vector2i> CBallMesh::GetSameColourNeighbourList(sf::Vector2i i2Point, BallType::Type eBallType)
{
	vector<sf::Vector2i> vi2Results;
	vi2Results.push_back(i2Point);
	int x = i2Point.x, y = i2Point.y;
	aeMeshSearchCopy[x][y] = BallType::Empty;
	if (x > 0 && y > 0 && aeMeshSearchCopy[x - 1][y - 1] == eBallType)
	{
		vector<sf::Vector2i> vi2SubResults = GetSameColourNeighbourList(sf::Vector2i(x - 1, y - 1), eBallType);
		vi2Results.insert(vi2Results.end(), vi2SubResults.begin(), vi2SubResults.end());
	}
	if (y > 0 && aeMeshSearchCopy[x][y - 1] == eBallType)
	{
		vector<sf::Vector2i> vi2SubResults = GetSameColourNeighbourList(sf::Vector2i(x, y - 1), eBallType);
		vi2Results.insert(vi2Results.end(), vi2SubResults.begin(), vi2SubResults.end());
	}
	if (x > 0 && aeMeshSearchCopy[x - 1][y] == eBallType)
	{
		vector<sf::Vector2i> vi2SubResults = GetSameColourNeighbourList(sf::Vector2i(x - 1, y), eBallType);
		vi2Results.insert(vi2Results.end(), vi2SubResults.begin(), vi2SubResults.end());
	}
	if (x + 1 < MESH_SIZE && aeMeshSearchCopy[x + 1][y] == eBallType)
	{
		vector<sf::Vector2i> vi2SubResults = GetSameColourNeighbourList(sf::Vector2i(x + 1, y), eBallType);
		vi2Results.insert(vi2Results.end(), vi2SubResults.begin(), vi2SubResults.end());
	}
	if (y + 1 < MESH_SIZE && aeMeshSearchCopy[x][y + 1] == eBallType)
	{
		vector<sf::Vector2i> vi2SubResults = GetSameColourNeighbourList(sf::Vector2i(x, y + 1), eBallType);
		vi2Results.insert(vi2Results.end(), vi2SubResults.begin(), vi2SubResults.end());
	}
	if (x + 1 < MESH_SIZE && y + 1 < MESH_SIZE && aeMeshSearchCopy[x + 1][y + 1] == eBallType)
	{
		vector<sf::Vector2i> vi2SubResults = GetSameColourNeighbourList(sf::Vector2i(x + 1, y + 1), eBallType);
		vi2Results.insert(vi2Results.end(), vi2SubResults.begin(), vi2SubResults.end());
	}
	return vi2Results;
}

CBallMesh::CBallMesh(void)
	: iBallCount(0), bHamsterWin(false), bHamsterLose(false)
{
	for (int i = 0 ; i < MESH_SIZE ; ++i)
	{
		for (int j = 0 ; j < MESH_SIZE ; ++j)
		{
			aeMesh[i][j] = BallType::Empty;
		}
	}
}

float CBallMesh::Distance(sf::Vector2f f2Point1, sf::Vector2f f2Point2)
{
	return (float)sqrt(pow((double)f2Point1.x - f2Point2.x, 2.0) + pow((double)f2Point1.y - f2Point2.y, 2.0));
}

sf::Vector2i CBallMesh::GetMapPosition(sf::Vector2f f2Point)
{
	int iHalfSize = MESH_SIZE / 2;
	sf::Vector2i i2Point;
	i2Point.y = int((f2Point.y - 384.0f) / (fBallSize * 0.8660254f) + iHalfSize + 0.5f);
	i2Point.x = int(floor(f2Point.x / fBallSize + 0.5f + float(i2Point.y - iHalfSize) * 0.5f)) + iHalfSize;
	return i2Point;
}

sf::Vector2f CBallMesh::GetWorldPosition(sf::Vector2i i2Point)
{
	i2Point.x -= MESH_SIZE / 2;
	i2Point.y -= MESH_SIZE / 2;
	return sf::Vector2f(fBallSize * i2Point.x - i2Point.y * fBallSize * 0.5f, 384.0f + fBallSize * i2Point.y * 0.8660254f);
}

bool CBallMesh::HasNeighbours(sf::Vector2i i2Pos)
{
	int x = i2Pos.x, y = i2Pos.y;
	if (x > 0 && y > 0 && aeMesh[x - 1][y - 1] != BallType::Empty)
	{
		return true;
	}
	if (y > 0 && aeMesh[x][y - 1] != BallType::Empty)
	{
		return true;
	}
	if (x > 0 && aeMesh[x - 1][y] != BallType::Empty)
	{
		return true;
	}
	if (x + 1 < MESH_SIZE && aeMesh[x + 1][y] != BallType::Empty)
	{
		return true;
	}
	if (y + 1 < MESH_SIZE && aeMesh[x][y + 1] != BallType::Empty)
	{
		return true;
	}
	if (x + 1 < MESH_SIZE && y + 1 < MESH_SIZE && aeMesh[x + 1][y + 1] != BallType::Empty)
	{
		return true;
	}
	return false;
}

sf::Vector2i CBallMesh::GetFirstNeighbour(sf::Vector2i i2Pos)
{
	int x = i2Pos.x, y = i2Pos.y;
	if (x > 0 && y > 0 && aeMesh[x - 1][y - 1] != BallType::Empty)
	{
		return sf::Vector2i(x - 1, y - 1);
	}
	if (y > 0 && aeMesh[x][y - 1] != BallType::Empty)
	{
		return sf::Vector2i(x, y - 1);
	}
	if (x > 0 && aeMesh[x - 1][y] != BallType::Empty)
	{
		return sf::Vector2i(x - 1, y);
	}
	if (x + 1 < MESH_SIZE && aeMesh[x + 1][y] != BallType::Empty)
	{
		return sf::Vector2i(x + 1, y);
	}
	if (y + 1 < MESH_SIZE && aeMesh[x][y + 1] != BallType::Empty)
	{
		return sf::Vector2i(x, y + 1);
	}
	if (x + 1 < MESH_SIZE && y + 1 < MESH_SIZE && aeMesh[x + 1][y + 1] != BallType::Empty)
	{
		return sf::Vector2i(x + 1, y + 1);
	}
	return sf::Vector2i(-1, -1);
}

void CBallMesh::Load(int iInitialLength, int iLevel)
{
	fBallSize = (float)GetBallImage(BallType::Red).GetWidth();
	for (int i = 0 ; i < iInitialLength ; ++i)
	{
		AddBall();
	}
	fNextBallTimer = 4.0f;
	fNewBallTimer = 4.0f;
}

void CBallMesh::Update(float fDelta)
{
	for (int i = 0 ; i < items.size() ; ++i)
	{
		sf::Vector2i i2Pos = GetMapPosition(items[i].getPosition());
		if (i2Pos.x < 0 || i2Pos.x >= MESH_SIZE || i2Pos.y < 0 || i2Pos.y >= MESH_SIZE)
		{
			continue;
		}
		if (HasNeighbours(i2Pos))
		{
			AddBall(i2Pos, items[i].getBallType());
			if (i2Pos.x == i2NewBallIndices.x && i2Pos.y == i2NewBallIndices.y)
			{
				fNextBallTimer = 4.0f;
				fNewBallTimer = 4.0f;
			}
			items.erase(items.begin() + i);
			for (int i = 0 ; i < MESH_SIZE ; ++i)
			{
				for (int j = 0 ; j < MESH_SIZE ; ++j)
				{
					aeMeshSearchCopy[i][j] = aeMesh[i][j];
				}
			}
			vector<sf::Vector2i> v2iNeighbourList = GetSameColourNeighbourList(i2Pos, aeMesh[i2Pos.x][i2Pos.y]);
			if (v2iNeighbourList.size() >= 3)
			{
				for (int i = 0 ; i < v2iNeighbourList.size() ; ++i)
				{
					aeMesh[v2iNeighbourList[i].x][v2iNeighbourList[i].y] = BallType::Empty;
				}
				iBallCount -= v2iNeighbourList.size();
				if (iBallCount == 0)
				{
					bHamsterWin = true;
				}
				else
				{
					ColourMesh();
					LeaveBiggestGroup();
				}
				if (!HasNeighbours(sf::Vector2i(i2NewBallIndices.x, i2NewBallIndices.y)))
				{
					fNextBallTimer = 4.0f;
					fNewBallTimer = 4.0f;
				}
			}
		}
	}

	if (fNextBallTimer > 0.0f)
	{
		fNextBallTimer -= fDelta;
		if (fNextBallTimer <= 0.0f)
		{
			i2NewBallIndices = FindFirstFree(false);
			eNewBall = BallType::Type(rand() % g_iBallCount);
		}
	}
	else
	{
		fNewBallTimer -= fDelta;
		if (fNewBallTimer <= 0.0f)
		{
			AddBall(i2NewBallIndices, eNewBall);
			fNextBallTimer = 4.0f;
			fNewBallTimer = 4.0f;
		}
	}
}

void CBallMesh::Display(void)
{
	sf::Sprite oBallSprite;
	for (int i = 0 ; i < MESH_SIZE ; ++i)
	{
		for (int j = 0 ; j < MESH_SIZE ; ++j)
		{
			if (aeMesh[i][j] != BallType::Empty)
			{
				sf::Image & oImage = GetBallImage(aeMesh[i][j]);
				oBallSprite.SetImage(oImage);
				oBallSprite.SetPosition(GetWorldPosition(sf::Vector2i(i, j)));
				oBallSprite.SetCenter(oImage.GetWidth() * 0.5f, oImage.GetHeight() * 0.5f);
				app.Draw(oBallSprite);
			}
		}
	}
	if (fNextBallTimer <= 0.0f)
	{
		sf::Image & oImage = GetBallImage(eNewBall);
		oBallSprite.SetImage(oImage);
		oBallSprite.SetPosition(GetWorldPosition(sf::Vector2i(i2NewBallIndices.x, i2NewBallIndices.y)));
		oBallSprite.SetCenter(oImage.GetWidth() * 0.5f, oImage.GetHeight() * 0.5f);
		float fTmp = 4.0f - fNewBallTimer;
		float fAlpha = fTmp - floor(fTmp);
		if (fTmp < 3.0f && fAlpha > 0.5f)
		{
			fAlpha = 1.0f - fAlpha;
		}
		oBallSprite.SetColor(sf::Color(255, 255, 255, sf::Uint8(fAlpha * 255)));
		app.Draw(oBallSprite);
	}
}

bool CBallMesh::HamsterWin(void)
{
	return bHamsterWin;
}

bool CBallMesh::HamsterLose(void)
{
	return bHamsterLose;
}
